/**
 * server.js
 * @author Ulyan Sobin
 * @description Cube server module
 */

"use strict";

const http = require("http");
const https = require("https");
const ws = require("ws");
const view = require("./view");
const { ErrorView } = view;
const web = require("./cube-web");

class CubeServer {

	/** @type {{
	 *   httpOptions: {
	 *     hostname: string,
	 *     port: number,
	 *   },
	 *   httpsOptions: {
	 *     hostname: string,
	 *     port: number,
	 *     cert: (string|ArrayBuffer),
	 *     key: (string|ArrayBuffer),
	 *     http_proxy: boolean,
	 *     http_proxy_port: number
	 *   },
	 *   handler: web.WebNode,
	 *   timeout: number
	 * }} */
	options;

	/**
	 * Constructor of `CubeServer`
	 * @param {{
	 *   httpOptions: {
	 *     hostname: string,
	 *     port: number,
	 *   },
	 *   httpsOptions: {
	 *     hostname: string,
	 *     port: number,
	 *     cert: (string|ArrayBuffer),
	 *     key: (string|ArrayBuffer),
	 *     http_proxy: boolean,
	 *     http_proxy_port: number
	 *   },
	 *   handler: web.WebNode,
	 *   timeout: number
	 * }} options 
	 */
	constructor (options) {
		const handler = options.handler;

		if (!(handler instanceof web.WebNode)) {
			throw new Error("Fatal error: `handler` must be instance of `WebNode`");
		}

		function httpListener(req, res) {
			try {
				let client = new web.WebHttpClient(
					new web.WebHttpRequest(req),
					new web.WebHttpResponse(res)
				);
				try {
					handler.route(client);
				} catch (err) {
					// Search for static
					let err_view = new ErrorView(404, "NOT FOUND");
					err_view.publish(client);
				}
			} catch (err) {
				console.error(`[ERROR] ${new Date().toISOString()} \'${req.url}\'::${err.message}`);
			}
		}

		let httpServer;

		if (typeof(options.httpOptions) == "object") {
			// Start http server
			let server = httpServer = http.createServer(httpListener);
			if (typeof(options.timeout) == "number")
				server.timeout = options.timeout;
			let port = options.httpOptions.port || 80;
			let hostname = options.httpOptions.hostname || "0.0.0.0";
			server.listen(port, hostname);
		}
		else if (typeof(options.httpsOptions) == "object") {
			// Start https server
			let server = httpServer = https.createServer({
				key: options.httpsOptions.key,
				cert: options.httpsOptions.cert
			}, httpListener);
			if (typeof(options.timeout) == "number")
				server.timeout = options.timeout;
			let port = options.httpsOptions.port || 443;
			let hostname = options.httpsOptions.hostname || "0.0.0.0";
			server.listen(port, hostname);

			if (options.httpsOptions.http_proxy === true) {
				let http_port = options.httpsOptions.http_proxy_port || 80;
				let http_hostname = options.httpsOptions.hostname || "0.0.0.0";
				let proxy_server = http.createServer((req, res) => {
					let redirect = `https://${req.headers.host}${req.url}`;
					res.writeHead(200, {"Content-Type":"text/html"});
					res.write("<!DOCTYPE html><html><head><script>window.location=\"" + redirect +
						"\";</script></head><body></body></html>");
					res.end();
				});
				proxy_server.listen(http_port, http_hostname);
			}
		}

		// Start ws server
		let wss = new ws.Server({ server: httpServer });
		wss.on("connection", (res, req) => {
			try {
				let client = new web.WebSocketClient(
					new web.WebSocketRequest(req),
					new web.WebSocketResponse(res)
				);
				try {
					handler.route(client);
				} catch (err) {
					console.log(`[WARNING] ${new Date().toISOString()} \'${req.url}\'::${err.message}`);
				}
			} catch (err) {
				console.error(`[ERROR] ${new Date().toISOString()} \'${req.url}\'::${err.message}`);
			}
		});

		this.options = options;
	}
}

module.exports.CubeServer = CubeServer;